// Emulates the old space invaders game by creating a "launcher"
// at the bottom of the screen that can move back and forth shooting
// rockets, and enters rapid fire mode when the left-mouse is held.

#ifdef __APPLE_CC__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <cstdlib>
#include <list>

using namespace std;

// This is the number of frames per second to render.
static const int FPS = 60;

// Set the bounds of the launcher and bullets
static const int MAX_OFFSET = 64;
static const int OFFSCREEN = 50;

// Check for rapid fire mode
static int rapidFiring = 0;
static const int FIRE_DELAY = 5;

// Color definitions
static GLfloat GREEN[] = {0.0, 1.0, 0.0};

// Custom Launcher class to handle the drawing of the... launcher.
class Launcher {
    int displayListID;

public:
    GLfloat offset;

    // Creates the launcher object ready to draw
    void create() {
        offset = 0;
        displayListID = glGenLists(1);
        glNewList(displayListID, GL_COMPILE);
        // Set the launcher's draw color to green
        glColor3fv(GREEN);
        glRectf(-10.0, -3.0, 10.0, 3.0);
        glRectf(-2, 3, 2, 6);
        glEndList();
    }

    // Invokes the display list to draw / redraw the launcher
    void draw() {
        glPushMatrix();
        glTranslatef(offset, -43.0, 0.0);
        glCallList(displayListID);
        glPopMatrix();
    }
};

// The launcher object that shoots the rockets
static Launcher launcher;

// Custom Bullet class to handle the drawing of the bullets
class Bullet {
public:
    GLfloat offsetY;
    GLfloat offsetX;

    // Creates the bullet object ready to draw, evaluating the
    // launcher's current offset
    void create(int x) {
        offsetX = x;
        offsetY = -45;
    }

    // Draws the current bullet
    void draw() {
        glPushMatrix();
        glColor3fv(GREEN);
        glRectf(offsetX - 0.5, offsetY + 6, offsetX + 0.5, offsetY + 8);
        glPopMatrix();
    }
};

// Record the Bullets in flight for drawing
list<Bullet> bullets;

// Handles the window reshape event by first ensuring that the viewport fills
// the entire drawing surface.  Then we use a simple orthographic projection
// with a logical coordinate system ranging from -50..50 in the smaller of
// the width or height, and scale the larger dimension to make the whole
// window isotropic.
void reshape(GLint w, GLint h) {
    glViewport(0, 0, w, h);
    GLfloat aspect = (GLfloat)w / (GLfloat)h;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h) {
        // width is smaller, go from -50 .. 50 in width
        glOrtho(-50.0, 50.0, -50.0/aspect, 50.0/aspect, -1.0, 1.0);
    } else {
        // height is smaller, go from -50 .. 50 in height
        glOrtho(-50.0*aspect, 50.0*aspect, -50.0, 50.0, -1.0, 1.0);
    }
}

// Handles the display callback
void display() {
    glClear(GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    launcher.draw();
    for (list<Bullet>::iterator i = bullets.begin(); i != bullets.end(); i++) {
        i->offsetY += 1;
        i->draw();
        if(i->offsetY > OFFSCREEN) {
            bullets.pop_front();
        }
    }
    glFlush();
    glutSwapBuffers();
}

// Handles the timer by checking for the rapidFiring state and
// then calling the redisplay, followed by itself again.
void timer(int v) {
    if (rapidFiring && v <= 0) {
        Bullet current;
        current.create(launcher.offset);
        bullets.push_back(current);
        v = FIRE_DELAY;
    } else {
        v--;
    }
    glutPostRedisplay();
    glutTimerFunc(1000 / FPS, timer, v);
}

// Handles the initial setup for the launcher and camera
void init() {
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    launcher.create();
}

// Handles mouse event of left click setting the firing state
void mouse(int button, int state, int x, int y) {
    if (button == GLUT_LEFT_BUTTON) {
        rapidFiring = !state;
    }
    return;
}

// Handles the keyboard inputs to slow, speed, or color the spinning square
void special(int key, int, int) {
    switch (key) {
        case GLUT_KEY_LEFT: if(launcher.offset >= -MAX_OFFSET) launcher.offset -= 2; break;
        case GLUT_KEY_RIGHT: if(launcher.offset <= MAX_OFFSET) launcher.offset += 2; break;
        default: return;
    }
}

// Initializes GLUT, the display mode, and main window; registers callbacks;
// enters the main event loop.
int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowPosition(80, 80);
    glutInitWindowSize(800, 500);
    glutCreateWindow("Retro Shooter");
    glutReshapeFunc(reshape);
    glutDisplayFunc(display);
    glutTimerFunc(1, timer, FIRE_DELAY);
    glutMouseFunc(mouse);
    glutSpecialFunc(special);
    init();
    glutMainLoop();
}
